﻿using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Rhino.UI;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace RhpCompile
{
    class Previewer : DisplayConduit
    {
        static Previewer _Instance;
        public static Previewer Instance
        {
            get
            {
                if (_Instance == null) _Instance = new Previewer();
                return _Instance;
            }
        }
        readonly MouseClick mouseClick;
        Previewer()
        {
            mouseClick = new MouseClick(this);
        }
        BoundingBox ClippingBox = BoundingBox.Empty;
        public void Clear()
        {
            LoopOn = false;
            previewObjects.Clear();
            ClippingBox = BoundingBox.Empty;
        }
        public void ClosePreview()
        {
            Clear();
            Enabled = false;
            mouseClick.Enabled = false;
            RhinoDoc.ActiveDoc.Views.Redraw();
        }
        public void EnableDraw()
        {
            Enabled = true;
            mouseClick.Enabled = true;
        }
        public static Color DefaultColor = Color.FromArgb(150, 2, 45, 80);
        readonly List<PreviewObject> previewObjects = new List<PreviewObject>();
        public bool IsEmpty => previewObjects.Count == 0 && (!LoopOn);
        public bool LoopOn { get; set; } = false;
        protected override void PostDrawObjects(DrawEventArgs e)
        {
            foreach (PreviewObject previewObject in previewObjects)
            {
                try
                {
                    if (previewObject != null && previewObject.Enable)
                    {
                        previewObject.Draw(e.Display);
                    }
                }
                catch
                {
                }
            }
        }
        const float buttonWidth = 140;
        const float buttonHeight = 35;
        public RectangleF PreviewRec { get; private set; } = RectangleF.Empty;
        static readonly Color fillColor = Color.FromArgb(0, 35, 67);
        static readonly Color edgeColor = Color.FromArgb(5, 60, 99);
        protected override void DrawOverlay(DrawEventArgs e)
        {
            if ((!IsEmpty) && e.Viewport.Id == RhinoDoc.ActiveDoc.Views.ActiveView.ActiveViewportID)
            {
                PreviewRec = new RectangleF(e.Viewport.Bounds.Width - (buttonWidth + buttonHeight * 0.2f), (buttonHeight * 0.2f), buttonWidth, buttonHeight);
                PointF PreviewRecCenter = new PointF(PreviewRec.X + PreviewRec.Width / 2, PreviewRec.Y + PreviewRec.Height / 2);
                e.Display.Draw2dText("Preview Off", Color.White, new Point2d(PreviewRecCenter.X, PreviewRecCenter.Y), true, 16, "Arial Black");
                e.Display.DrawRoundedRectangle(PreviewRecCenter, PreviewRec.Width, PreviewRec.Height, 5, edgeColor, 4, fillColor);
            }
        }
        protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
        {
            e.IncludeBoundingBox(ClippingBox);
        }
        public void Preview(Vector3d vector3d, Point3d from, Color color)
        {
            if (from.IsValid && vector3d.IsValid && (!vector3d.IsZero))
            {
                PreviewVector previewVector = new PreviewVector(vector3d, from, color);
                previewObjects.Add(previewVector);
                ClippingBox.Union(previewVector.ClippingBox);
            }
        }
        public void Preview(object geo, Color color)
        {
            switch (geo)
            {
                case Point3d pt:
                    if (pt.IsValid)
                    {
                        previewObjects.Add(new PreviewPoint(pt, color));
                    }
                    break;
                case Point3f ptf:
                    if (ptf.IsValid)
                    {
                        previewObjects.Add(new PreviewPoint(new Point3d(ptf), color));
                    }
                    break;
                case PointCloud cloud:
                    if (cloud.IsValid)
                    {
                        previewObjects.Add(new PreviewPointCloud(cloud, color));
                    }
                    break;
                case Line line:
                    if (line.IsValid)
                        previewObjects.Add(new PreviewStructGeos(line, color));
                    break;
                case Box box:
                    if (box.IsValid)
                        previewObjects.Add(new PreviewBox(box, color));
                    break;
                case BoundingBox bbox:
                    if (bbox.IsValid)
                        previewObjects.Add(new PreviewBox(new Box(bbox), color));
                    break;
                case Circle circle:
                    if (circle.IsValid)
                        previewObjects.Add(new PreviewCircle(circle, color));
                    break;
                case Arc arc:
                    if (arc.IsValid)
                        previewObjects.Add(new PreviewArc(arc, color));
                    break;
                case Ellipse ellipse:
                    if (ellipse.IsValid)
                        previewObjects.Add(new PreviewCurve(ellipse.ToNurbsCurve(), color));
                    break;
                case Cone cone:
                    if (cone.IsValid)
                        previewObjects.Add(new PreviewCone(cone, color));
                    break;
                case Rectangle3d rectangle:
                    if (rectangle.IsValid)
                        previewObjects.Add(new DrawPolyline(rectangle.ToPolyline(), color));
                    break;
                case Polyline polyline:
                    if (polyline.IsValid && polyline.Count > 1)
                        previewObjects.Add(new DrawPolyline(polyline, color));
                    break;
                case Cylinder cylinder:
                    if (cylinder.IsValid)
                        previewObjects.Add(new DrawCylinder(cylinder, color));
                    break;
                case Plane plane:
                    if (plane.IsValid)
                        previewObjects.Add(new PreviewPlane(plane, color));
                    break;
                case Sphere sphere:
                    if (sphere.IsValid)
                        previewObjects.Add(new DrawSphere(sphere, color));
                    break;
                case AnnotationBase annotationBase:
                    previewObjects.Add(new PreviewAnnotationBase(annotationBase, color));
                    break;
                case Hatch hatch:
                    previewObjects.Add(new PreviewHatch(hatch, color));
                    break;
                case TextDot textDot:
                    previewObjects.Add(new PreviewTextDot(textDot, color));
                    break;
                case Curve crv:
                    previewObjects.Add(new PreviewCurve(crv, color));
                    break;
                case Text3d text3D:
                    previewObjects.Add(new PreviewText3d(text3D, color));
                    break;
                case GeometryBase geometry:
                    previewObjects.Add(new PreviewGeometryBase(geometry, color));
                    break;
                case RhinoObject rhinoObject:
                    previewObjects.Add(new PreviewRhinoObject(rhinoObject, color));
                    break;
            }
            if (previewObjects.Count > 0)
                ClippingBox.Union(previewObjects[previewObjects.Count - 1].ClippingBox);
        }
    }
    #region PreviewerObject
    abstract class PreviewObject
    {
        public bool Enable { get; private set; } = true;
        public Color DrawColor { get; set; }
        public BoundingBox ClippingBox { get; protected set; }
        public PreviewObject(Color drawColor)
        {
            DrawColor = drawColor;
        }
        public abstract void Draw(DisplayPipeline display);
    }
    class PreviewVector : PreviewObject
    {
        readonly Line line;
        public PreviewVector(Vector3d vec, Point3d pt, Color color) : base(color)
        {
            line = new Line(pt, vec + pt);
            ClippingBox = line.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawArrow(line, DrawColor);
        }
    }
    class PreviewGeometryBase : PreviewObject
    {
        readonly Mesh[] previewMesh;
        readonly DisplayMaterial material;
        readonly bool m_DrawMeshVerticesColor = false;
        public PreviewGeometryBase(GeometryBase geo, Color color) : base(color)
        {
            material = new DisplayMaterial(color, 1 - color.A / 255.0);
            switch (geo)
            {
                case Mesh mesh:
                    previewMesh = new Mesh[] { mesh.DuplicateMesh() };
                    m_DrawMeshVerticesColor = mesh.VertexColors.Count > 0;
                    break;
                case Extrusion extrusion:
                    previewMesh = Mesh.CreateFromBrep(extrusion.ToBrep(), MeshingParameters.FastRenderMesh);
                    break;
                case Brep brep:
                    previewMesh = Mesh.CreateFromBrep(brep, MeshingParameters.FastRenderMesh);
                    break;
                case Surface surface:
                    previewMesh = new Mesh[] { Mesh.CreateFromSurface(surface, MeshingParameters.FastRenderMesh) };
                    break;
                default:
                    previewMesh = Array.Empty<Mesh>();
                    break;
            }
            if (previewMesh != null)
            {
                foreach (Mesh mesh in previewMesh)
                {
                    if (mesh.FaceNormals.Count == 0 || mesh.Normals.Count == 0)
                    {
                        mesh.RebuildNormals();
                        mesh.FaceNormals.ComputeFaceNormals();
                    }
                }
            }
            ClippingBox = geo.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            if (m_DrawMeshVerticesColor)
            {
                foreach (Mesh mesh in previewMesh)
                {
                    display.DrawMeshFalseColors(mesh);
                }
            }
            else
            {
                foreach (Mesh mesh in previewMesh)
                {
                    display.DrawMeshShaded(mesh, material);
                }
            }
        }
    }
    class PreviewAnnotationBase : PreviewObject
    {
        readonly AnnotationBase annotationBase;
        public PreviewAnnotationBase(AnnotationBase annotationBase, Color color) : base(color)
        {
            this.annotationBase = annotationBase.Duplicate() as AnnotationBase;
            ClippingBox = annotationBase.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawAnnotation(annotationBase, DrawColor);
        }
    }
    class PreviewRhinoObject : PreviewObject
    {
        readonly RhinoObject rhinoObject;
        public PreviewRhinoObject(RhinoObject rhinoObject, Color color) : base(color)
        {
            this.rhinoObject = rhinoObject;
            ClippingBox = rhinoObject.Geometry.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawObject(rhinoObject);
        }
    }
    class PreviewTextDot : PreviewObject
    {
        readonly TextDot textDot;
        public PreviewTextDot(TextDot text, Color color) : base(color)
        {
            textDot = text.Duplicate() as TextDot;
            ClippingBox = text.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.Draw2dText(textDot.Text, DrawColor, textDot.Point, true, textDot.FontHeight, textDot.FontFace);
        }
    }
    class PreviewHatch : PreviewObject
    {
        readonly Hatch geo;
        public PreviewHatch(Hatch geo, Color color) : base(color)
        {
            this.geo = geo.Duplicate() as Hatch;
            ClippingBox = geo.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawHatch(geo, DrawColor, DrawColor);
        }
    }
    class PreviewText3d : PreviewObject
    {
        readonly Text3d text3d;
        public PreviewText3d(Text3d text3d, Color color) : base(color)
        {
            this.text3d = text3d;
            ClippingBox = text3d.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.Draw3dText(text3d, DrawColor);
        }
    }
    class PreviewCurve : PreviewObject
    {
        readonly Curve geo;
        public PreviewCurve(Curve geo, Color color) : base(color)
        {
            this.geo = geo.DuplicateCurve();
            ClippingBox = geo.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawCurve(geo, DrawColor);
        }
    }
    class DrawPolyline : PreviewObject
    {
        readonly Polyline geo;
        public DrawPolyline(Polyline geo, Color color) : base(color)
        {
            this.geo = geo.Duplicate();
            ClippingBox = geo.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawPolyline(geo, DrawColor);
        }
    }
    class PreviewPlane : PreviewObject
    {
        Plane plane;
        public PreviewPlane(Plane plane, Color color) : base(color)
        {
            this.plane = plane;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.Viewport.GetWorldToScreenScale(plane.Origin, out double pixelsPerUnit);
            double scale = 20 / pixelsPerUnit;
            display.DrawLine(plane.Origin, plane.Origin + plane.XAxis * scale, Color.Red);
            display.DrawLine(plane.Origin, plane.Origin + plane.YAxis * scale, Color.Green);
            display.DrawLine(plane.Origin, plane.Origin + plane.ZAxis * scale, Color.Blue);
            scale *= 0.5;
            Hatch hatch = Hatch.Create(new PolylineCurve(new Point3d[] { plane.Origin, plane.PointAt(scale, 0), plane.PointAt(scale, scale), plane.PointAt(0, scale), plane.Origin }), 0, 0, 1, 0.1)[0];
            display.DrawHatch(hatch, DrawColor, DrawColor);
        }
    }
    class PreviewStructGeos : PreviewObject
    {
        Line line;
        public PreviewStructGeos(Line line, Color color) : base(color)
        {
            this.line = line;
            ClippingBox = line.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawLine(line, DrawColor);
        }
    }
    class PreviewPoint : PreviewObject
    {
        Point3d pt;
        public PreviewPoint(Point3d pt, Color color) : base(color)
        {
            this.pt = pt;
            ClippingBox = new BoundingBox(pt, pt + new Vector3d(0.1, 0.1, 0.1));
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawPoint(pt, PointStyle.Simple, 3, DrawColor);
        }
    }
    class PreviewPointCloud : PreviewObject
    {
        readonly PointCloud cloud;
        public PreviewPointCloud(PointCloud cloud, Color color) : base(color)
        {
            this.cloud = cloud;
            ClippingBox = cloud.GetBoundingBox(false);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawPointCloud(cloud, 1, DrawColor);
        }
    }
    class PreviewBox : PreviewObject
    {
        Box box;
        public PreviewBox(Box box, Color color) : base(color)
        {
            this.box = box;
            ClippingBox = box.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawBox(box, DrawColor);
        }
    }
    class PreviewCircle : PreviewObject
    {
        Circle circle;
        public PreviewCircle(Circle circle, Color color) : base(color)
        {
            this.circle = circle;
            ClippingBox = circle.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawCircle(circle, DrawColor);
        }
    }
    class PreviewArc : PreviewObject
    {
        Arc arc;
        public PreviewArc(Arc arc, Color color) : base(color)
        {
            this.arc = arc;
            ClippingBox = arc.BoundingBox();
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawArc(arc, DrawColor);
        }
    }
    class PreviewCone : PreviewObject
    {
        Cone geo;
        public PreviewCone(Cone geo, Color color) : base(color)
        {
            this.geo = geo;
            ClippingBox = new BoundingBox(geo.ApexPoint, geo.ApexPoint);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawCone(geo, DrawColor);
        }
    }
    class DrawCylinder : PreviewObject
    {
        Cylinder geo;
        public DrawCylinder(Cylinder geo, Color color) : base(color)
        {
            this.geo = geo;
            ClippingBox = new BoundingBox(geo.Center, geo.Center + geo.BasePlane.ZAxis * geo.TotalHeight);
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawCylinder(geo, DrawColor);
        }
    }
    class DrawSphere : PreviewObject
    {
        Sphere geo;
        public DrawSphere(Sphere geo, Color color) : base(color)
        {
            this.geo = geo;
            ClippingBox = geo.BoundingBox;
        }
        public override void Draw(DisplayPipeline display)
        {
            display.DrawSphere(geo, DrawColor);
        }
    }
    #endregion
    class MouseClick : MouseCallback
    {
        readonly Previewer owner;
        public MouseClick(Previewer owner)
        {
            this.owner = owner;
        }
        bool _mouseDown = false;
        protected override void OnEndMouseUp(MouseCallbackEventArgs e)
        {
            if (_mouseDown && owner.PreviewRec.Contains(e.ViewportPoint))
            {
                _mouseDown = false;
                owner.ClosePreview();
            }
        }
        protected override void OnMouseDown(MouseCallbackEventArgs e)
        {
            if ((!owner.IsEmpty) && owner.PreviewRec.Contains(e.ViewportPoint))
            {
                e.Cancel = true;
                _mouseDown = true;
                RhinoDoc.ActiveDoc.Views.Redraw();
            }
        }
    }
}
